<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>Demo6 - softboy vulume</title>

    <style>
        body {
            margin: 0;
            text-align: center;
        }

        #info {
            position: absolute;
            top: 0;
            width: 100%;
            padding: 10px;
        }

    </style>

    <script src="js/three.js"></script>
    <script src="js/libs/ammo.js"></script>
    <script src="js/controls/OrbitControls.js"></script>
    <script src="js/libs/stats.min.js"></script>
    <script src="js/Detector.js"></script>
</head>
<body>

<div id="container"><br/><br/><br/><br/><br/>Loading...</div>
<div id="info">Demo6 - 有体积的柔体</div>

</body>

<script>
    "use strict";

    // 检测浏览器是否支持webgl
    if (!Detector.webgl) {
        Detector.addGetWebGLMessage();
        document.getElementById('container').innerHTML = '';
    }

    // 全局变量

    // 绘图相关变量
    var container, stats;
    var camera, controls, scene, renderer;
    var textureLoader;
    var clock = new THREE.Clock();
    var terrainMesh;

    // 物理引擎相关变量
    var gravityConstant = -9.8;
    var collisionConfiguration;
    var dispatcher;
    var broadphase;
    var solver;
    var physicsWorld;
    var rigidBodies = [];
    var margin = 0.05;
    var transformAux1 = new Ammo.btTransform();

    //
    var time = 0;

    // 鼠标输入相关
    var mouseCoords = new THREE.Vector2();
    var raycaster = new THREE.Raycaster();
    var ballMaterial = new THREE.MeshPhongMaterial({color: 0x202020});

    var maxNumObjectss = 30;

    // 柔体物理相关
    var hinge;
    var cloth;
    var softBodySolver;
    var softBodies = [];
    var softBodyHelpers = new Ammo.btSoftBodyHelpers();

    // 支架旋转
    var armMovement = 0;


    // - 主程序
    init();
    animate();


    // -函数定义
    function init() {

        initGraphics();

        initPhysics();

        createObjects();

        initInput();
    }

    function initGraphics() {
        // three.js基本场景配置
        container = document.getElementById('container');
        container.innerHTML = "";

        camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
        camera.position.x = -7;
        camera.position.y = 5;
        camera.position.z = 8;

        controls = new THREE.OrbitControls(camera);
        controls.target.y = 2;

        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setClearColor(0xbfd1e5);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;

        textureLoader = new THREE.TextureLoader();

        // 场景
        scene = new THREE.Scene();

        // 环境光
        var ambientLight = new THREE.AmbientLight(0x404040);
        scene.add(ambientLight);

        // 线性光
        var light = new THREE.DirectionalLight(0xffffff, 1);
        light.position.set(-10, 10, 5);
        light.castShadow = true;
        var d = 10;
        light.shadow.camera.left = -d;
        light.shadow.camera.right = d;
        light.shadow.camera.top = d;
        light.shadow.camera.bottom = -d;

        light.shadow.camera.near = 2;
        light.shadow.camera.far = 2;

        light.shadow.mapSize.x = 1024;
        light.shadow.mapSize.y = 1024;

        scene.add(light);

        container.appendChild(renderer.domElement);

        // 显示帧数
        stats = new Stats();
        stats.domElement.style.position = 'absolute';
        stats.domElement.style.top = 'absolute';
        container.appendChild(stats.domElement);

        // 添加窗口大小变化监听
        window.addEventListener('resize', onWindowResize, false);

//        // 添加坐标轴
//        var axis = new THREE.AxisHelper(200);
//        scene.add(axis);

    }

    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();

        renderer.setSize(window.innerWidth, window.innerHeight);

    }

    function initPhysics() {
        // bullet基本场景配置
//        collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
        /**
         * 启动支持肉体的物理引擎
         * config，solver和physicsWorld要用softbody版本
         * 还要单独创建softBodySolver
         */
        collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
        dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
        broadphase = new Ammo.btDbvtBroadphase();
        solver = new Ammo.btSequentialImpulseConstraintSolver();
        softBodySolver = new Ammo.btDefaultSoftBodySolver();
        physicsWorld = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver);
        physicsWorld.setGravity(new Ammo.btVector3(0, gravityConstant, 0));

        physicsWorld.getWorldInfo().set_m_gravity(new Ammo.btVector3(0, gravityConstant, 0));
    }

    function createObjects() {
        var pos = new THREE.Vector3();
        var quat = new THREE.Quaternion();

        // 创建地面
        pos.set(0, -0.5, 0);
        quat.set(0, 0, 0, 1);
        var ground = createParallellepiped(40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial({color: 0xffffff}));
        ground.castShadow = true;       // 开启投影
        ground.receiveShadow = true;    // 接受阴影(可以在表面上显示阴影)
        textureLoader.load("./textures/grid.png", function (texture) {
            texture.wrapS = THREE.RepeatWrapping;
            texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set(40, 40);
            ground.material.map = texture;
            ground.material.needsUpdate = texture;
        });

//        // 生成30个随机物体
//        for (var i = 0; i < maxNumObjectss; i++) {
//            pos.set(Math.random(), 2 *i, Math.random());
//            quat.set(0, 0, 0, 1);
//
//            createRondomObject(pos, quat, Math.ceil(Math.random() * 3));
//        }

//        // 创建一个球
//        var ballMass =1.2;
//        var ballRadius = 0.6;
//
//        var ball = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 20, 20), new THREE.MeshPhongMaterial( { color: 0x202020 } ));
//        ball.castShadow = true;
//        ball.receiveShadow = true;
//        var ballShape = new Ammo.btSphereShape( ballRadius );
//        ballShape.setMargin( margin );
//        pos.set( -3, 2, 0 );
//        quat.set( 0, 0, 0, 1 );
//        createRigidBody( ball, ballShape, ballMass, pos, quat );
//        ball.userData.physicsBody.setFriction( 0.5 );

        // 创建砖墙
        var brickMass = 0.5;
        var brickLength = 1.2;
        var brickDepth = 0.6;
        var brickHeight = brickLength * 0.5;
        var numBricksLength = 6;
        var numBricksHeight = 8;
        var z0 = -numBricksLength * brickLength * 0.5;
        pos.set(0, brickHeight * 0.5, z0);
        quat.set(0, 0, 0, 1);
        for (var j = 0; j < numBricksHeight; j++) {
            var oddRow = ( j % 2 ) == 1;
            pos.z = z0;
            if (oddRow) {
                pos.z -= 0.25 * brickLength;
            }
            var nRow = oddRow ? numBricksLength + 1 : numBricksLength;
            for (var i = 0; i < nRow; i++) {
                var brickLengthCurrent = brickLength;
                var brickMassCurrent = brickMass;
                if (oddRow && ( i == 0 || i == nRow - 1 )) {
                    brickLengthCurrent *= 0.5;
                    brickMassCurrent *= 0.5;
                }
                var brick = createParallellepiped(brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createRendomColorObjectMeatrial());
                brick.castShadow = true;
                brick.receiveShadow = true;
                if (oddRow && ( i == 0 || i == nRow - 2 )) {
                    pos.z += 0.75 * brickLength;
                }
                else {
                    pos.z += brickLength;
                }
            }
            pos.y += brickHeight;
        }

        // 创建坡道
        pos.set(3, 1, 0);
        quat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), 30 * Math.PI / 180);
        var obstacle = createParallellepiped(10, 1, 4, 0, pos, quat, new THREE.MeshPhongMaterial({color: 0x606060}));
        obstacle.castShadow = true;
        obstacle.receiveShadow = true;

        // 创建拥有体积的柔体
        var volumeMass = 15;

        var sphereGeometry = new THREE.SphereBufferGeometry(1.5, 40, 25);
        sphereGeometry.translate(5, 5, 0);
        createSoftVolume(sphereGeometry, volumeMass, 250);
        var boxGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(1, 1, 5, 4, 4, 20));
        boxGeometry.translate(-2, 5, 0);
        createSoftVolume(boxGeometry, volumeMass, 120);
    }

    // 生成随机形状的物体
    function createRondomObject(pos, quat, objectSize) {
        var numTypes = 4;
        var objectType = Math.ceil(Math.random() * numTypes);

        var threeObject = null;
        var shape = null;

//        var objectSize = 3;

        switch (objectType) {
            case 1:
                // Sphere
                var radius = 1 + Math.random() * objectSize;
                threeObject = new THREE.Mesh(new THREE.SphereGeometry(radius, 20, 20), createRendomColorObjectMeatrial());
                shape = new Ammo.btSphereShape(radius);
                shape.setMargin(margin);
                break;
            case 2:
                // Box
                var sx = 1 + Math.random() * objectSize;
                var sy = 1 + Math.random() * objectSize;
                var sz = 1 + Math.random() * objectSize;
                threeObject = new THREE.Mesh(new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1), createRendomColorObjectMeatrial());
                shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
                shape.setMargin(margin);
                break;
            case 3:
                // Cylinder
                var radius = 1 + Math.random() * objectSize;
                var height = 1 + Math.random() * objectSize;
                threeObject = new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, height, 20, 1), createRendomColorObjectMeatrial());
                shape = new Ammo.btCylinderShape(new Ammo.btVector3(radius, height * 0.5, radius));
                shape.setMargin(margin);
                break;
            default:
                // Cone
                var radius = 1 + Math.random() * objectSize;
                var height = 2 + Math.random() * objectSize;
                threeObject = new THREE.Mesh(new THREE.CylinderGeometry(0, radius, height, 20, 2), createRendomColorObjectMeatrial());
                shape = new Ammo.btConeShape(radius, height);
                break;
        }

        var mass = objectSize * 5; // 体积越大质量越大

        createRigidBody(threeObject, shape, mass, pos, quat);

    }

    // 生成随机颜色材质
    function createRendomColorObjectMeatrial() {
        var color = Math.floor(Math.random() * (1 << 24));
        return new THREE.MeshPhongMaterial({color: color});
    }

    function createParallellepiped(sx, sy, sz, mass, pos, quat, material) {
        var threeObject = new THREE.Mesh(new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1), material);
        var shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
        shape.setMargin(margin);

        createRigidBody(threeObject, shape, mass, pos, quat);

        return threeObject;
    }

    function createRigidBody(threeObject, physicsShape, mass, pos, quat) {
        threeObject.position.copy(pos);
        threeObject.quaternion.copy(quat);

        var transform = new Ammo.btTransform();
        transform.setIdentity();
        transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
        transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
        var motionState = new Ammo.btDefaultMotionState(transform);

        var localInertia = new Ammo.btVector3(0, 0, 0);
        physicsShape.calculateLocalInertia(mass, localInertia);

        var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
        var body = new Ammo.btRigidBody(rbInfo);

        threeObject.userData.physicsBody = body;

        scene.add(threeObject);

        if (mass > 0) {
            rigidBodies.push(threeObject);

            // Disable deactivation
            // 防止物体弹力过快消失

            // Ammo.DISABLE_DEACTIVATION = 4
            body.setActivationState(4);
        }

        physicsWorld.addRigidBody(body);

        return body;
    }

    function animate() {
        requestAnimationFrame(animate);

        render();

        stats.update();

    }

    function render() {
        var deltaTime = clock.getDelta();

        updatePhysics(deltaTime);

        controls.update(deltaTime);

        renderer.render(scene, camera);

        time += deltaTime;
    }

    function updatePhysics(deltaTime) {
        physicsWorld.stepSimulation(deltaTime, 10); // 减少迭代次数提高性能

        // 更新物体位置
        for (var i = 0, iL = rigidBodies.length; i < iL; i++) {
            var objThree = rigidBodies[i];
            var objPhys = objThree.userData.physicsBody;
            var ms = objPhys.getMotionState();
            if (ms) {
                ms.getWorldTransform(transformAux1);
                var p = transformAux1.getOrigin();
                var q = transformAux1.getRotation();
                objThree.position.set(p.x(), p.y(), p.z());
                objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
            }
        }

        // 更新柔体状态
        for (var i = 0, iL = softBodies.length; i < iL; i++) {
            var volume = softBodies[i];
            var geometry = volume.geometry;
            var softBody = volume.userData.physicsBody;
            var volumePositions = geometry.attributes.position.array;
            var volumeNormals = geometry.attributes.normal.array;
            var association = geometry.ammoIndexAssociation;
            var numVerts = association.length;
            var nodes = softBody.get_m_nodes();

            for (var j = 0; j < numVerts; j++) {
                var node = nodes.at(j);
                var nodePos = node.get_m_x();
                var x = nodePos.x();
                var y = nodePos.y();
                var z = nodePos.z();
                var nodeNormal = node.get_m_n();
                var nx = nodeNormal.x();
                var ny = nodeNormal.y();
                var nz = nodeNormal.z();
                var assocVertex = association[j];
                for (var k = 0, kl = assocVertex.length; k < kl; k++) {
                    var indexVertex = assocVertex[k];
                    volumePositions[indexVertex] = x;
                    volumeNormals[indexVertex] = nx;
                    indexVertex++;
                    volumePositions[indexVertex] = y;
                    volumeNormals[indexVertex] = ny;
                    indexVertex++;
                    volumePositions[indexVertex] = z;
                    volumeNormals[indexVertex] = nz;
                }
            }

            geometry.attributes.position.needsUpdate = true;
            geometry.attributes.normal.needsUpdate = true;
        }
    }

    function initInput() {

        window.addEventListener('mousedown', function (event) {

            mouseCoords.set(
                    ( event.clientX / window.innerWidth ) * 2 - 1,
                    -( event.clientY / window.innerHeight ) * 2 + 1
            );

            raycaster.setFromCamera(mouseCoords, camera);

            // Creates a ball and throws it
            var ballMass = 3; // 把球质量减轻,防止力量过大让柔体破面进而完全无法计算
            var ballRadius = 0.4;

            var ball = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 14, 10), ballMaterial);
            ball.castShadow = true;
            ball.receiveShadow = true;
            var ballShape = new Ammo.btSphereShape(ballRadius);
            ballShape.setMargin(margin);
            var pos = new THREE.Vector3();
            var quat = new THREE.Quaternion();
            pos.copy(raycaster.ray.direction);
            pos.add(raycaster.ray.origin);
            quat.set(0, 0, 0, 1);
            var ballBody = createRigidBody(ball, ballShape, ballMass, pos, quat);

            pos.copy(raycaster.ray.direction);
            pos.multiplyScalar(24);
            ballBody.setLinearVelocity(new Ammo.btVector3(pos.x, pos.y, pos.z));

        }, false);

    }

//    // 生成带体积的柔体的函数
//
    //fun1 判断误差范围内两向量相等的函数
    function isEqual(x1, y1, z1, x2, y2, z2) {
        var delta = 0.000001;
        return  Math.abs(x1 - x2) < delta &&
                Math.abs(y1 - y2) < delta &&
                Math.abs(z1 - z2) < delta;
    }
//
    function createIndexedBufferGeometryFromGeometry(geometry) {
        var numVertices = geometry.vertices.length;
        var numFaces = geometry.faces.length;

        var bufferGeom = new THREE.BufferGeometry();
        var vertices = new Float32Array(numVertices * 3);
        var indices = new ( numFaces * 3 > 65535 ? Uint32Array : Uint16Array )(numFaces * 3);

        for (var i = 0; i < numVertices; i++) {
            var p = geometry.vertices[i];

            var i3 = i * 3;

            vertices[i3] = p.x;
            vertices[i3 + 1] = p.y;
            vertices[i3 + 2] = p.z;
        }

        for (var i = 0; i < numFaces; i++) {
            var f = geometry.faces[i];

            var i3 = i * 3;

            indices[i3] = f.a;
            indices[i3 + 1] = f.b;
            indices[i3 + 2] = f.c;
        }

        bufferGeom.setIndex(new THREE.BufferAttribute(indices, 1));
        bufferGeom.addAttribute('position', new THREE.BufferAttribute(vertices, 3));

        return bufferGeom;
    }


    function mapIndices(bufGeometry, indexedBufferGeom) {
        // Creates ammoVertices, ammoIndices and ammoIndexAssociation in bufGeometry
        var vertices = bufGeometry.attributes.position.array;
        var idxVertices = indexedBufferGeom.attributes.position.array;
        var indices = indexedBufferGeom.index.array;

        var numIdxVertices = idxVertices.length / 3;
        var numVertices = vertices.length / 3;

        bufGeometry.ammoVertices = idxVertices;
        bufGeometry.ammoIndices = indices;
        bufGeometry.ammoIndexAssociation = [];

        for (var i = 0; i < numIdxVertices; i++) {
            var association = [];
            bufGeometry.ammoIndexAssociation.push(association);

            var i3 = i * 3;

            for (var j = 0; j < numVertices; j++) {
                var j3 = j * 3;
                if (isEqual(idxVertices[i3], idxVertices[i3 + 1], idxVertices[i3 + 2],
                                vertices[j3], vertices[j3 + 1], vertices[j3 + 2])) {
                    association.push(j3);
                }
            }
        }
    }

    function processGeometry(bufGeometry) {
        // Obtain a Geometry
//        var geometry = new THREE.Geometry().fromBufferGeometry(bufGeometry);
        var geometry = new THREE.Geometry().fromBufferGeometry( bufGeometry );

        // Merge the vertices so the triangle soup is converted to indexed triangles
//        var vertsDiff = geometry.mergeVertices();
        geometry.mergeVertices();

        // Convert again to BufferGeometry, indexed
//        var indexedBufferGeom = createIndexedBufferGeometryFromGeometry(geometry);
        var indexedBufferGeom = createIndexedBufferGeometryFromGeometry(geometry);

        // Create index arrays mapping the indexed vertices to bufGeometry vertices
        mapIndices(bufGeometry, indexedBufferGeom);
    }

    function createSoftVolume(bufferGeom, mass, pressure) {
        processGeometry(bufferGeom);

        // 渲染空间中的柔体
        var volume = new THREE.Mesh(bufferGeom, new THREE.MeshPhongMaterial({color: 0x00EE00}));
        volume.castShadow = true;
        volume.receiveShadow = true;
        volume.frustumCulled = false;
        scene.add(volume);

        // 物理空间中的柔体
        var volumeSoftBody = softBodyHelpers.CreateFromTriMesh(
                physicsWorld.getWorldInfo(),
                bufferGeom.ammoVertices,
                bufferGeom.ammoIndices,
                bufferGeom.ammoIndices.length / 3,
                true);

        var sbConfig = volumeSoftBody.get_m_cfg();
        sbConfig.set_viterations(40);
        sbConfig.set_piterations(40);

        // Soft-soft and soft-rigid collisions
        sbConfig.set_collisions(0x11);

        // Friction 摩擦因数
        sbConfig.set_kDF(0.1);
        // Damping 阻尼
        sbConfig.set_kDP(0.01);
        // Pressure 压力
        sbConfig.set_kPR(pressure);
        // Stiffness 强度
        volumeSoftBody.get_m_materials().at(0).set_m_kLST(0.9);
        volumeSoftBody.get_m_materials().at(0).set_m_kAST(0.9);

        // Soft-soft and soft-rigid collisions
        sbConfig.set_collisions(0x11);

        volumeSoftBody.setTotalMass(mass, false);
        Ammo.castObject(volumeSoftBody, Ammo.btCollisionObject).getCollisionShape().setMargin(margin);
        physicsWorld.addSoftBody(volumeSoftBody, 1, -1);
        volume.userData.physicsBody = volumeSoftBody;

        // Disable deactivation
        // Ammo.DISABLE_DEACTIVATION = 4
        volumeSoftBody.setActivationState(4);

        softBodies.push(volume);
    }
</script>

</html>